package com.arcticcoding.telemetrie;

import java.io.IOException;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

import com.arcticcoding.dto.ImageFrame;
import com.arcticcoding.telemetrie.comm.CameraCommand;
import com.arcticcoding.telemetrie.comm.CameraResponse;
import com.arcticcoding.telemetrie.comm.TelemetrieCommand;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryonet.Connection;
import com.esotericsoftware.kryonet.Listener;
import com.esotericsoftware.kryonet.Server;

/**
 * A specialized KryoNet Server, this is a server sending Telemetrie data to all connected clients.
 * 
 * @author steffenschuhmann
 *
 */
public class TelemetrieServer extends Service {
	private static final String TAG = "com.arcticcoding.nxtphone.TelemetrieServer";
	public static final int TCP_PORT = 2045;
	public static final int UDP_PORT = 2046;
	private Server server;
	private final IBinder mBinder = new TelemetrieServerBinder(); 
	private TelemetrieProvider provider;
	
	private TelemetrieCallback callback = new TelemetrieCallback() {
		
		/**
		 * {@inheritDoc}
		 * 
		 * This implementation of the geoServiceValueHasChanged sends the 
		 * new geo data to all connected clients using the sendLocationData
		 * mmethod of the TelemetrieServer.
		 */
		@Override
		public void geoServiceValueHasChanged(com.arcticcoding.dto.Location newLocation) {
			Log.d(TAG, "New location");
			sendLocationData(newLocation);
		}
		
		/**
		 * {@inheritDoc}
		 * 
		 * This implementation of the cameraServiceValueHasChanged sends the 
		 * image to all connected client using the sendImage method
		 * of the TelemetrieServer.
		 */
		@Override
		public void cameraServiceValueHasChanged(byte[] imageData) {
			ImageFrame frame;
			Log.d(TAG, "Cameradata has changed");
			frame = new ImageFrame(imageData);
			
			sendImage(frame);
		}
	};

	/**
	 * {@inheritDoc}
	 */
	@Override
	public IBinder onBind(Intent intent) {	
		Log.d(TAG, "Binder send");
		return mBinder;
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void onCreate() {
		super.onCreate();
		Log.d(TAG, "onCreate");
		Kryo kryo;
		server = new Server(TelemetrieValues.OBJECTBUFFERSIZE, 
				TelemetrieValues.OBJECTBUFFERSIZE);
		server.start();
		server.addListener(new ServerListener());
		kryo = server.getKryo();
		
		for(@SuppressWarnings("rawtypes") Class c: TelemetrieValues.KRYO_CLASSES) {
			kryo.register(c);
		}
		
		provider = new TelemetrieProvider(getApplicationContext());
		provider.setTelemetrieCallback(callback);
		Log.d(TAG, "Server initialized");
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void onDestroy() {
		close();
		super.onDestroy();
	}
	
	/**
	 * Bind this server to the specified <b>UDP and TCP</b> port. 
	 * If this ports a not available this methos will throw an IOException.
	 *  
	 * @param tcpPort The port which should be used for the TCP communication
	 * @param udpPort The port which should be used for the UDP communication
	 * @return true if the server was bound
	 * @throws IOException If the ports are not available
	 */
	public boolean bindServer(int tcpPort, int udpPort) throws IOException {
		if(!(server.getConnections().length > 0)) {
			server.bind(tcpPort, udpPort);
			return true;
		} else {
			Log.d(TAG, "Clients connected, can't rebind");
			return false;
		}
	}
	
	/**
	 * Close this TelemetrieServer with all it's streams and 
	 * threads.
	 */
	public void close() {
		server.close();
		provider.stop();
	}
	
	/**
	 * Sends the location object to all registered clients over the
	 * TCP connection.
	 * 
	 * @param location
	 */
	public void sendLocationData(com.arcticcoding.dto.Location location) {
		try {
			if(server.getConnections().length > 0) {
				server.sendToAllUDP(location);
				Log.d(TAG, "Location send");
			} else {
				Log.d(TAG, "No one is connected");
			}
		} catch(Exception e) {
			Log.d(TAG, "Can't send location");
		}
	}

	/**
	 * Sends an ImageFrame to all registered clients over the
	 * UDP connection.
	 * 
	 * @param frame New ImageFrame
	 */
	public void sendImage(ImageFrame frame) {
		try {
			if (server.getConnections().length > 0) {
				CameraResponse response;
				
				response = new CameraResponse(CameraResponse.Type.IMAGE_FRAME, frame);
				server.sendToAllUDP(response);
				Log.d(TAG, "Image send");
			} else {
				Log.d(TAG, "No on e is connected");
			}
		} catch (Exception e) {
			Log.d(TAG, "Can't send image");
		}
	}
	
	/**
	 * {@inheritDoc} 
	 * @author steffenschuhmann
	 * @version 0.01
	 *
	 */
	public class TelemetrieServerBinder extends Binder {
		public TelemetrieServer getService() {
			return TelemetrieServer.this;
		}
	}
	
	/**
	 * 
	 * @author steffenschuhmann
	 * @version 0.01
	 */
	private class ServerListener extends Listener {
		
		/**
		 * {@inheritDoc}
		 */
		@Override
		public void connected(Connection conn) {
			Log.d(TAG, "New connection");
			super.connected(conn);
		}
		
		/**
		 * {@inheritDoc}
		 */
		@Override
		public void disconnected(Connection conn) {
			Log.d(TAG, "Device disconnected");
			super.disconnected(conn);
		}
		
		/**
		 * {@inheritDoc}
		 */
		@Override
		public void received(Connection conn, Object o) {
			if(o instanceof TelemetrieCommand) {
				if (o instanceof CameraCommand) {
					CameraCommand command = (CameraCommand) o;
					CameraService service = (CameraService) provider
							.getService(TelemetrieProvider.CAMERA_SERVICE);

					if (service != null) {

						switch (command.type) {
						case GET_SUPPORTED_FRAME_RATES:
							//TODO Get framerates and send response
							break;
						case GET_SUPPORTED_RESOLUTIONS:
							//TODO Get resolutions and send response
							break;
						case LED_OFF:
							service.ledOff();
							Log.d(TAG, "Led off");
							break;
						case LED_ON:
							service.ledOn();
							Log.d(TAG, "LED on");
							break;
						case START_STREAMING:
							service.startStreaming();
							Log.d(TAG, "Start streaming");
							break;
						case STOP_STREAMING:
							service.stopStreaming();
							Log.d(TAG, "Stop streaming");
							break;
						default:
							Log.d(TAG, "Unknown camera command");
							break;
						}
					} else {
						Log.d(TAG, "No CameraService running");
					}
				} else {
					Log.d(TAG, "Unknown TelemetrieCommand: " + o.toString());
				}
			} else {
				Log.d(TAG, "Unknown command received: " + o.toString());
			}
			super.received(conn, o);
		}
	}
}
